package com.hm.base.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hm.base.auto.SystemEnvConfigProperties;
import com.hm.base.auto.su.BeanUtil;
import com.hm.base.auto.su.R.RedisKey;
import com.hm.base.domain.Menu;
import com.hm.base.domain.Role;
import com.hm.base.domain.RoleMenu;
import com.hm.base.domain.Subscriber;
import com.hm.base.domain.SubscriberRole;
import com.hm.base.mapper.DataAuthMapper;
import com.hm.base.mapper.MenuMapper;
import com.hm.base.mapper.RoleMapper;
import com.hm.base.mapper.RoleMenuMapper;
import com.hm.base.mapper.SubscriberMapper;
import com.hm.base.mapper.SubscriberRoleMapper;
import com.hm.base.vo.LoginSubscriberVo;
import com.hm.base.vo.MenuVo;
import com.hm.base.vo.OrganizationVo;
import com.hm.base.vo.RoleMenuVo;
import com.hm.base.vo.RoleVo;
import com.hm.common.def.DataStatusDef;
import com.hm.common.def.WhetherDef;
import com.hm.common.exception.ErrorCode;
import com.hm.common.exception.ServiceException;
import com.hm.common.util.CommonUtil;
import com.hm.common.util.DateUtil;
import com.hm.common.util.EncryptUtil.AES;
import com.hm.common.util.EncryptUtil.Md5;
import com.hm.common.util.StringUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author shishun.wang
 * @date 下午4:58:25 2017年6月2日
 * @version 1.0
 * @describe
 */
@Slf4j
@Service
@Transactional
public class AuthorizationServiceImpl implements AuthorizationService {

	@Autowired
	private RoleMapper roleMapper;

	@Autowired
	private MenuMapper menuMapper;

	@Autowired
	private TaskExecutor taskExecutor;

	@Autowired
	private DataAuthMapper dataAuthMapper;

	@Autowired
	private RoleMenuMapper roleMenuMapper;

	@Autowired
	private SubscriberMapper subscriberMapper;

	@Autowired
	private SubscriberRoleMapper subscriberRoleMapper;

	@Autowired
	private SystemEnvConfigProperties systemEnvConfigProperties;

	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	@Override
	public LoginSubscriberVo login(final String username, String password) {
		if (CommonUtil.isAnyEmpty(username, password)) {
			throw ServiceException.warning(ErrorCode.PARAMETERS_MISSING, "登陆用户名密码");
		}

		Subscriber subscriber = new Subscriber();
		subscriber.setAccount(username.trim());
		subscriber.setUserPwd(Md5.getMD5Code(password.trim()));

		subscriber = subscriberMapper.login(subscriber);
		if (CommonUtil.isEmpty(subscriber)) {
			String errorCount = redisTemplate.opsForValue().get(RedisKey.SUBSCRIBER_LOGIN_ERROR_GROUP + username.trim());
			if (StringUtil.isEmpty(errorCount)) {
				redisTemplate.opsForValue().set(RedisKey.SUBSCRIBER_LOGIN_ERROR_GROUP + username.trim(), "1");
			} else {
				if (Integer.parseInt(errorCount) >= systemEnvConfigProperties.getLoginErrorMaxCount()) {

					taskExecutor.execute(new Runnable() {

						@Override
						public void run() {
							Subscriber errorSubscriber = subscriberMapper.checkAccount(username.trim());
							if (null != errorSubscriber) {
								errorSubscriber.setLoginLock(WhetherDef.YES.name());
								subscriberMapper.updateByPrimaryKey(errorSubscriber);
							}
						}
					});

					throw ServiceException.warn("该账户已被禁止登陆");
				} else {
					redisTemplate.opsForValue().set(RedisKey.SUBSCRIBER_LOGIN_ERROR_GROUP + username.trim(), (Integer.parseInt(errorCount) + 1) + "");
					redisTemplate.expire(RedisKey.SUBSCRIBER_LOGIN_ERROR_GROUP + username.trim(), 60 * 60 * 24, TimeUnit.SECONDS);
				}
			}
			throw ServiceException.warn("用户名/密码错误");
		}

		if (DataStatusDef.DISABLE.name().equals(subscriber.getStatus())) {
			throw ServiceException.warn("该账户已被注销");
		}

		if (WhetherDef.YES.name().equals(subscriber.getLoginLock())) {
			throw ServiceException.warn("该账户已被禁止登陆");
		}

		final LoginSubscriberVo subscriberVo = new LoginSubscriberVo();
		BeanUtil.copyProperties(subscriber, subscriberVo);
		subscriberVo.setLastLoginTime(DateUtil.data2long(subscriber.getLastLoginTime()));

		long now = System.currentTimeMillis();
		subscriberVo.setToken(AES.encrypt(subscriber.getId() + ":" + now));
		subscriberVo.setCurrentLoginTime(now);

		{// 加载用户角色ids
			List<Long> roleIds = new ArrayList<Long>();
			roleMapper.loadUserRoles(subscriber.getId()).forEach(role -> {
				roleIds.add(role.getId());
			});
			subscriberVo.setRoles(roleIds);
		}

		final Long userId = subscriber.getId();
		redisTemplate.opsForValue().set(RedisKey.SUBSCRIBER_LOGIN_GROUP + userId, JSON.toJSONString(subscriberVo));
		return subscriberVo;
	}

	@Override
	public void logout(Long subscriberId, String username) {
		redisTemplate.opsForValue().set(RedisKey.SUBSCRIBER_LOGIN_GROUP + subscriberId, null);
	}

	@Override
	public List<RoleVo> loadUserRoles(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		List<Role> roles = roleMapper.loadUserRoles(subscriberId);
		if (CommonUtil.isEmpty(roles)) {
			return new ArrayList<RoleVo>(0);
		}

		return roles.parallelStream().map(role -> {
			RoleVo vo = new RoleVo();
			BeanUtil.copyProperties(role, vo);

			return vo;
		}).collect(Collectors.toList());
	}

	@Override
	public List<MenuVo> loadUserMenus(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		return loopUserMenus(subscriberId, 0l);
	}

	private List<MenuVo> loopUserMenus(Long subscriberId, Long parentId) {
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("subscriberId", subscriberId);
		param.put("parentId", parentId);

		List<Menu> menus = menuMapper.loadUserMenus(param);
		if (CommonUtil.isEmpty(menus)) {
			return new ArrayList<MenuVo>(0);
		}

		return menus.parallelStream().map(menu -> {
			MenuVo vo = new MenuVo();
			BeanUtil.copyProperties(menu, vo);
			vo.setChilds(loopUserMenus(subscriberId, menu.getId()));

			return vo;
		}).collect(Collectors.toList());
	}

	@Override
	public LoginSubscriberVo loadLoginToken(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		String cacheObj = redisTemplate.opsForValue().get(RedisKey.SUBSCRIBER_LOGIN_GROUP + subscriberId);

		if (CommonUtil.isEmpty(cacheObj)) {
			return null;
		}

		return JSONObject.parseObject(cacheObj.toString(), LoginSubscriberVo.class);
	}

	@Override
	public List<OrganizationVo> loadUserOrganizations(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		return loopUserOrganizations(subscriberId, 0l);
	}

	private List<OrganizationVo> loopUserOrganizations(Long subscriberId, Long parentId) {

		return null;
	}

	@Override
	public List<RoleVo> loadUserOrganizationRoles(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		List<Role> roles = roleMapper.loadUserOrganizationRoles(subscriberId);
		if (CommonUtil.isEmpty(roles)) {
			return new ArrayList<RoleVo>(0);
		}

		return roles.parallelStream().map(role -> {
			RoleVo vo = new RoleVo();
			BeanUtil.copyProperties(role, vo);

			return vo;
		}).collect(Collectors.toList());
	}

	@Override
	public List<MenuVo> loadUserOrganizationMenus(Long subscriberId) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		return loopUserOrganizationMenus(subscriberId, 0l);
	}

	private List<MenuVo> loopUserOrganizationMenus(Long subscriberId, Long parentId) {
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("subscriberId", subscriberId);
		param.put("parentId", parentId);

		List<Menu> menus = menuMapper.loadUserOrganizationMenus(param);
		if (CommonUtil.isEmpty(menus)) {
			return new ArrayList<MenuVo>(0);
		}

		return menus.parallelStream().map(menu -> {
			MenuVo vo = new MenuVo();
			BeanUtil.copyProperties(menu, vo);
			vo.setChilds(loopUserMenus(subscriberId, menu.getId()));

			return vo;
		}).collect(Collectors.toList());
	}

	@Override
	public void settingUserOrganizations(Long subscriberId, List<Long> organizations) {
	}

	@Override
	public void settingUserOrganizationRoles(Long organizationId, List<Long> roles) {
	}

	@Override
	public void settingUserRoles(Long subscriberId, List<Long> roles) {
		if (CommonUtil.isEmpty(subscriberId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		SubscriberRole subscriberRole = new SubscriberRole();
		subscriberRole.setSubscriberId(subscriberId);
		subscriberRoleMapper.delete(subscriberRole);

		if (CommonUtil.isEmpty(roles)) {
			return;
		}

		subscriberRoleMapper.insertList(roles.parallelStream().map(roleId -> {
			SubscriberRole model = new SubscriberRole();
			{
				model.setSubscriberId(subscriberId);
				model.setRoleId(roleId);
			}

			return model;
		}).collect(Collectors.toList()));
	}

	@Override
	public void settingRoleMenus(Long roleId, List<Long> menus) {

		if (CommonUtil.isEmpty(roleId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		Role role = roleMapper.selectByPrimaryKey(roleId);
		if (CommonUtil.isEmpty(role)) {
			throw ServiceException.warning(ErrorCode.DATA_NOT_FOUND, "角色");
		}

		if (WhetherDef.trance(role.getSystemDefault()) == WhetherDef.YES) {
			throw ServiceException.warning(ErrorCode.NO_DATA_ACCESS);
		}

		RoleMenu roleMenu = new RoleMenu();
		roleMenu.setRoleId(roleId);
		roleMenuMapper.delete(roleMenu);

		if (CommonUtil.isEmpty(menus)) {
			return;
		}

		roleMenuMapper.insertList(menus.parallelStream().map(menuId -> {
			RoleMenu model = new RoleMenu();
			{
				model.setMenuId(menuId);
				model.setRoleId(roleId);
			}

			return model;
		}).collect(Collectors.toList()));
	}

	@Override
	public List<RoleMenuVo> loadRoleMenus(Long roleId) {
		if (CommonUtil.isEmpty(roleId)) {
			throw ServiceException.warning(ErrorCode.REQUIRED_PARAMETERS_MISSING);
		}

		List<RoleMenuVo> vos = new ArrayList<RoleMenuVo>();
		loadRoleMenus(roleId, 0l, vos);

		return vos;
	}

	private void loadRoleMenus(Long roleId, Long menuParentId, List<RoleMenuVo> vos) {
		Menu menu = new Menu();
		menu.setParentId(menuParentId);
		menu.setStatus(DataStatusDef.ENABLE.name());

		menuMapper.select(menu).forEach(item -> {
			RoleMenu roleMenu = new RoleMenu();
			{
				roleMenu.setRoleId(roleId);
				roleMenu.setMenuId(item.getId());
			}

			RoleMenuVo vo = new RoleMenuVo();
			vo.setId(item.getId());
			vo.setPId(menuParentId);
			vo.setName(item.getName());
			vo.setOpen(false);
			vo.setChecked(CommonUtil.isNotEmpty(roleMenuMapper.select(roleMenu)));

			vos.add(vo);

			this.loadRoleMenus(roleId, item.getId(), vos);
		});

	}

	@Override
	public boolean remoteClienAuthCodeCheck(String authCode, List<Long> roles) {
		if (CommonUtil.isEmpty(roles)) {
			throw ServiceException.warning(ErrorCode.NO_DATA_ACCESS);
		}

		for (Long roleId : roles) {
			String key = RedisKey.SYSTEM_DATA_AUTH_PREFIX + ":" + roleId;
			String dataAuthCache = redisTemplate.opsForValue().get(key);
			if (StringUtil.isBlankOrNull(dataAuthCache)) {
				continue;
			}
			if (JSON.parseArray(dataAuthCache, String.class).contains(authCode)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean refreshSystemRoleDataAuth() {
		roleMapper.selectAll().parallelStream().forEach(role -> {
			if (DataStatusDef.trance(role.getStatus()) != DataStatusDef.ENABLE) {
				return;
			}

			Map<String, Object> param = new HashMap<String, Object>();
			{
				param.put("roleId", role.getId());
			}
			List<String> dataAuths = dataAuthMapper.getRoleDataAuths(param);
			if (CommonUtil.isEmpty(dataAuths)) {
				return;
			}
			String key = RedisKey.SYSTEM_DATA_AUTH_PREFIX + ":" + role.getId();
			log.info("刷新数据权限缓存key[{}],数据值{}", key, dataAuths.toString());
			redisTemplate.opsForValue().set(key, JSON.toJSONString(dataAuths));
			// redisTemplate.boundSetOps(key).add(dataAuths.toArray(new String[0]));
		});

		return true;
	}

}
